package com.itcoon.cloud.framework.transform.handler;


import com.itcoon.cloud.framework.model.KeyValue;
import com.itcoon.cloud.framework.transform.AbstractTransformHandler;
import com.itcoon.cloud.framework.transform.DefaultMethodAssembler;
import com.itcoon.cloud.framework.transform.InjectHandler;
import com.itcoon.cloud.framework.transform.MethodAssembler;
import com.itcoon.cloud.framework.transform.config.TransformConfigurationProperties;
import com.itcoon.cloud.framework.transform.constants.TransformFeatureConstants;
import com.itcoon.cloud.framework.transform.converter.ConverterFactory;
import com.itcoon.cloud.framework.transform.definition.InjectDefinition;
import com.itcoon.cloud.framework.transform.definition.ModelDefinition;
import com.itcoon.cloud.framework.transform.definition.ModelPropertyDefinition;
import com.itcoon.cloud.framework.transform.enums.TransformFeature;
import com.itcoon.cloud.framework.transform.utils.TransformServiceUtils;
import com.itcoon.cloud.framework.utils.SpringReflectionUtils;
import com.itcoon.cloud.framework.utils.SpringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Miaoxia Yu
 * @date 2020-10-28
 */
public class ClazzTransformHandler<T, R> extends AbstractTransformHandler<T, R> {

    private static final Logger log = LoggerFactory.getLogger(ClazzTransformHandler.class);
    private static final InjectHandler INJECT_HANDLER = new InjectHandler();
    private volatile static ClazzTransformHandler INSTANCE = null;

    public static <T, R> ClazzTransformHandler<T, R> getInstance(TransformConfigurationProperties properties){
        if(INSTANCE == null){
            synchronized (ClazzTransformHandler.class){
                if(INSTANCE == null){
                    INSTANCE = new ClazzTransformHandler<>(properties);
                }
            }
        }
        return INSTANCE;
    }

    private ClazzTransformHandler(TransformConfigurationProperties properties) {
        super(properties);
    }


    @Override
    protected R doHandle(ModelDefinition md, Class<R> targetClass, Type[] actualTypes, R target, T source, String[] ignoreProperties, List<InjectDefinition> injectDefinitions, TransformFeature... features) {
        try {
            for (ModelPropertyDefinition mpd : md.getPropertyDefinitions()) {
                if(Arrays.asList(ignoreProperties).contains(mpd.getTargetAttribute())){
                    continue;
                }
                // 获取原目标的值
                Object sourceValue = null;
                InjectDefinition injectDefinition = INJECT_HANDLER.support(mpd.getTargetAttribute(), injectDefinitions);
                if(injectDefinition != null){
                    sourceValue = INJECT_HANDLER.handle(targetClass, injectDefinition, source);
                }else{
                    if(mpd.getOriginGetter() == null && mpd.getOriginField() == null){
                        continue;
                    }
                    sourceValue = getOriginPropertyValue(source, mpd.getOriginGetter(), mpd.getOriginField());
                }
                TransformFeature transformWay = getTransformWay(features);
                transformWay = transformWay == null ? TransformFeature.BY_FIELD : transformWay;
                if(sourceValue == null){
                    if(Arrays.stream(features).anyMatch(feature-> feature == TransformFeature.INCLUDE_NULL)){
                        doPropertySet(sourceValue, target, mpd.getTargetField(), mpd.getTargetSetter(), transformWay);
                    }
                    continue;
                }

                Field targetField = mpd.getTargetField();
                Method targetSetter = mpd.getTargetSetter();
                Type targetPropertyType = null;
                if(transformWay == TransformFeature.BY_FIELD){
                    if(targetField != null){
                        targetPropertyType = targetField.getGenericType();
                    }
                }else if(transformWay == TransformFeature.BY_METHOD){
                    if(targetSetter != null){
                       targetPropertyType = targetSetter.getGenericParameterTypes()[0];
                    }
                }else {
                    if (targetField != null) {
                        targetPropertyType = targetField.getGenericType();
                    } else if (targetSetter != null) {
                        targetPropertyType = targetSetter.getGenericParameterTypes()[0];
                    }
                }
                if(targetPropertyType != null){
                    if(transformWay != TransformFeature.BY_JSON){
                        List<String> ignorePropertyList = Arrays.stream(ignoreProperties).filter(s-> s.contains(".") && s.split("\\.")[0].equals(mpd.getTargetAttribute())).map(s->s.substring(s.indexOf(".") + 1)).collect(Collectors.toList());
                        List<InjectDefinition> filterInjectDefinitions = filterInjectDefinition(injectDefinitions, mpd.getTargetAttribute());
                        KeyValue keyValue = TransformServiceUtils.explain(targetPropertyType, targetClass, actualTypes);
                        Object assembleValue = sourceValue;
                        if(keyValue.getKey() != null){
                            assembleValue = doAssemble(sourceValue, (Class<R>)keyValue.getKey(), (Type[]) keyValue.getValue(), ignorePropertyList.toArray(new String[0]), filterInjectDefinitions, features);
                        }
                        doPropertySet(assembleValue, target, mpd.getTargetField(), mpd.getTargetSetter(), transformWay);
                    }
                }
            }
        } catch (IllegalAccessException | InvocationTargetException e) {
            log.error(e.getMessage(),e);
            throw new RuntimeException(e.getMessage());
        }
        return target;
    }

    private <T1, R1> R1 doAssemble(T1 source, Class<R1> targetClass, Type[] actualTypes, String[] ignoreProperties, List<InjectDefinition> injectDefinitions, TransformFeature... features){
        MethodAssembler<T1, R1> methodAssembler = new DefaultMethodAssembler<>(SpringUtils.getBean(ConverterFactory.class));
        return methodAssembler.assemble(source, targetClass, actualTypes, ignoreProperties, injectDefinitions, features);
    }

    private Object getOriginPropertyValue(T source, Method originGetter, Field field) throws InvocationTargetException, IllegalAccessException {
        Object methodParameterValue = null;
        if(originGetter != null){
            methodParameterValue = originGetter.invoke(source);
        }
        if(field != null){
            methodParameterValue = SpringReflectionUtils.getField(source, field.getName());
        }
        return methodParameterValue;
    }

    private <V> void doPropertySet(V sourceValue, R target, Field targetField, Method targetSetter, TransformFeature feature) throws InvocationTargetException, IllegalAccessException {
        if(feature != null){
            switch (feature){
                case BY_METHOD:
                    if(targetSetter != null){
                        targetSetter.invoke(target, sourceValue);
                    }
                    break;
                case BY_FIELD:
                    if(targetField != null){
                        targetField.setAccessible(true);
                        targetField.set(target, sourceValue);
                    }
                    break;
                case BY_JSON:
                    throw new UnsupportedOperationException();
            }

        }
        if(targetField != null){
            targetField.setAccessible(true);
            targetField.set(target, sourceValue);
        }else if(targetSetter != null){
            targetSetter.invoke(target, sourceValue);
        }
    }

    private TransformFeature getTransformWay(TransformFeature... features){
        return Arrays.stream(features).filter(TransformFeatureConstants.TRANSFORM_TYPES::contains).findAny().orElse(TransformFeature.BY_FIELD);
    }

    private List<InjectDefinition> filterInjectDefinition(List<InjectDefinition> injectDefinitions, String targetAttribute){
        List<InjectDefinition> results = new ArrayList<>();
        for (InjectDefinition definition : injectDefinitions) {
            String attribute = definition.getAttribute();
            if(attribute.contains(".") && attribute.split("\\.")[0].equals(targetAttribute)){
                attribute = attribute.substring(attribute.indexOf(".") + 1);
                InjectDefinition newDefinition = new InjectDefinition(attribute, definition.getInjectMode(), definition.getSource());
                results.add(newDefinition);
            }
        }
        return results;
    }
}
